﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Pfz.Caching;
using Pfz.Extensions.MonitorLockExtensions;

namespace Pfz.Threading
{
	/// <summary>
	/// This is a thread pool that, different from System.Threading.ThreadPool,
	/// does not have a thread limit and the threads are not background while
	/// they run. Comparing to the system ThreadPool, it is better if the
	/// thread can enter in wait mode. This happens when one thread has a 
	/// "fast" process, but can only terminate after another thread returns.
	/// 
	/// This thread pool keeps threads that were used alive at the next collection.
	/// So, for a thread to die, it must not be used between 2 collections.
	/// </summary>
	public static class UnlimitedThreadPool
	{
		#region Private fields
			private static object _lock = new object();
			private static List<Parameters> _freeEvents = new List<Parameters>();
		#endregion
		
		#region Constructor
			[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
			static UnlimitedThreadPool()
			{
				GCUtils.Collected += _Collected;
			}
		#endregion
		#region _Collected
			private static void _Collected()
			{
				try
				{
					int minimumCollectionNumber = GC.CollectionCount(GC.MaxGeneration);
					List<Parameters> freeEvents;
					
					lock(_lock)
					{
						freeEvents = _freeEvents;
							
						var newFreeEvents = new List<Parameters>();
						foreach(Parameters p in freeEvents)
						{
							if (p.UsedInThisGeneration)
							{
								p.UsedInThisGeneration = false;
								newFreeEvents.Add(p);
							}
							else
							{
								p.Action = null;
								p.WaitEvent.Set();
							}
						}
						_freeEvents = newFreeEvents;
					}
				}
				catch
				{
				}
			}
		#endregion
		
		#region Methods
			#region Run
				/// <summary>
				/// Runs an action in another thread. Uses an existing thread if one is 
				/// available or creates a new one if none are available, so this call will
				/// not hang if there are no available threads.
				/// </summary>
				/// <param name="action">The function to execute.</param>
				public static void Run(Action action)
				{
					Run
					(
						delegate(object obj)
						{
							action();
						},
						null
					);
				}
				
				/// <summary>
				/// Runs an action in another thread. Uses an existing thread if one is 
				/// available or creates a new one if none are available, so this call will
				/// not hang if there are no available threads.
				/// </summary>
				/// <param name="action">The function to execute.</param>
				/// <param name="parameter">The object passed as parameter to the action.</param>
				public static void Run(Action<object> action, object parameter)
				{
					if (action == null)
						throw new ArgumentNullException("action");
				
					Parameters p = null;
					lock(_lock)
					{
						int count = _freeEvents.Count;
						if (count > 0)
						{
							int index = count - 1;
							p = _freeEvents[index];
							_freeEvents.RemoveAt(index);
						}
					}
							
					if (p == null)
					{
						p = new Parameters();
						p.WaitEvent = new ManagedManualResetEvent();
						Thread thread = new Thread(_RunThread);
						p.Thread = thread;
						thread.Start(p);
					}

					p.Action = action;
					p.Parameter = parameter;
					
					p.Thread.IsBackground = false;
					p.WaitEvent.Set();
				}
				
				/// <summary>
				/// Runs an action in another thread. Uses an existing thread if one is 
				/// available or creates a new one if none are available, so this call will
				/// not hang if there are no available threads.
				/// </summary>
				/// <typeparam name="T">The type of the parameter.</typeparam>
				/// <param name="action">The function to execute.</param>
				/// <param name="parameter">The object passed as parameter to the action.</param>
				public static void Run<T>(Action<T> action, T parameter)
				{
					Run
					(
						delegate(object obj)
						{
							T typedParameter = (T)obj;
							action(typedParameter);
						},
						parameter
					);
				}
			#endregion
			
			#region _RunThread
				private static void _RunThread(object parameters)
				{
					Thread currentThread = Thread.CurrentThread;
					Parameters p = (Parameters)parameters;
					var waitEvent = p.WaitEvent;
					
					try
					{
						currentThread.IsBackground = true;

						while(true)
						{
							waitEvent.WaitOne();
							
							if (p.Action == null)
								return;
							
							currentThread.IsBackground = false;
							p.Action(p.Parameter);

							if (currentThread.Name != null)
								return;

							p.UsedInThisGeneration = true;
							waitEvent.Reset();
							currentThread.IsBackground = true;

							lock(_lock)
								_freeEvents.Add(p);
						}
					}
					finally
					{
						if (currentThread.IsBackground)
							lock(_lock)
								_freeEvents.Remove(p);

						waitEvent.Dispose();
					}
				}
			#endregion
		#endregion

		#region Parameters - Nested class
			private sealed class Parameters
			{
				internal Thread Thread;
				internal ManagedManualResetEvent WaitEvent;
				internal Action<object> Action;
				internal object Parameter;
				internal bool UsedInThisGeneration;
			}
		#endregion
	}
}
